package ipcards;
import ipcards.gui.TablePanel;
import ipcards.net.Commands;
import ipcards.net.Connection;

import java.util.*;
import java.util.List;
import java.awt.*;
import java.io.IOException;

public class PlayerModel implements Player, PlayerListener {
	
	private TablePanel tablepanel;
	private Connection connection;
	private Controller controller;
	
	private CardSet selectedCards;
	private Point prevPoint;
	
	
	private CardSet heldCards = new CardSet();	//cards controlled by selector
	
	//put this in table panel when lock is available
	private static int snapX = 18;
	private static int snapY = 24;
	private static int snapRatio = 4;
	private boolean snap = true;
	
	
	public PlayerModel(Controller controller) {
		this.controller = controller;
		selectedCards = new CardSet();
	}
	
	public PlayerModel(PlayerListener listener, Connection connection, Controller controller) {
		this(connection, controller);
		setPlayerListener(listener);
	}
	
	public PlayerModel(Connection connection, Controller controller) {
		this(controller);
		this.connection = connection;
	}
	
	public void setTablePanel(TablePanel tp) {
		this.tablepanel = tp;
		alert();
	}
	
	public void updateView() {
		Dbg.println("PlayerModel.updateView");
		if (tablepanel!=null) {
			tablepanel.repaint();
		}
	}
	
	public String getName() {
		return "default name";
	}
	
	
	public void alert(){
		Controller.debug.update(getModel().getRoom().getTable().getCards());
		Dbg.println("PlayerModel.alert");
		updateView();
	}
	
	public void sendInfoAcrossServer() {
		try {
			connection.syncToServer();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NullPointerException e) {
			//e.printStackTrace();
		}
	}
	
	
	
	/**
	* Set a {@link Card} as the only selected card.
	* 
	* @param card The <code>Card</code> to be selected.
	*/
	public boolean selectSingleCard(Card card) /*throws IllegalArgumentException*/ {
		deselectAllCards();
		boolean val = selectCard(card);
		select(selectedCards);
		
		return val;
	}
	
	/**
	* Add a {@link Card} to those selected.
	* 
	* @param card The <code>Card</code> to be selected.
	*/
	public boolean selectCard(Card card) /*throws IllegalArgumentException*/ {
		
		if (card == null) {
			return false;
		}
				
		selectedCards.add(card);
		select(selectedCards);
	
		return false;
	}
	
	/**
	Replaces the current selected cards with the given set of cards
	*/
	public boolean selectCards(Set<Card> cards) /*throws IllegalArgumentException*/ {
		
		if (cards == null) {
			return false;
		}
		
		selectedCards = new CardSet(cards);
		select(selectedCards);
		
		return false;
	}
	
	/**
	 * Sends selected cards to the server
	 * @param ids
	 */
	public void sendSelect(int[] ids) {
		String args = "";
		
		//deselect all cards
		if (ids == null) {
			try {
				connection.sendCommand(Commands.CMD_DESELECT_ALL, null);
				return;
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		
		for(int id: ids) {
			args += id + Commands.DEL;
		}
		
		try {
			connection.sendCommand(Commands.CMD_SELECT, args);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public void sendDeselect () {
		try {
			connection.sendCommand(Commands.CMD_DESELECT_ALL, null);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public void sendMoveBy(Point point) {
		try {
			connection.sendCommand(Commands.CMD_MOVE_BY, point.x + Commands.DEL + point.y);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	/**
	* Remove a {@link Card} from those selected.
	* 
	* @param card The <code>Card</code> to be deselected.
	*/
	public void deselectCard(Card card) {
		if (card==null) {
			// uncomment the following line once exception handling is implemented in the View
			//throw IllegalArgumentException("card cannot be null");
		}
		selectedCards.remove(card);
		select(selectedCards);
	}
	
	public void toggleSelectCard(Card card) {
		if (card==null) {
			// uncomment the following line once exception handling is implemented in the View
			//throw IllegalArgumentException("card cannot be null");
		}
		if (isSelectedCard(card)) {
			deselectCard(card);
		} else {
			selectCard(card);
		}
		select(selectedCards);
	}
	
	
	public void deselectAllCards() {
		selectedCards = new CardSet();
		select(selectedCards);
		
		//send to server
		sendSelect(null);
	}
	
	/**
	 * Locally deselects all cards. Does not send to server.
	 */
	public void deselectAllCardsLocal() {
		selectedCards = new CardSet();
		select(selectedCards);
		sendSelect(null);
	}
	
	/**
	 * Flips the currently selected {@link Card}.
	 * 
	 */
	public void flipSelected() {
		
		flip();
		
		//send flip to server
		try {
			connection.sendCommand(Commands.CMD_FLIP, null);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public void shuffleSelected(){
		shuffle();
		alert();
		try {
			connection.syncToServer();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
	
	/**
	 * Tests if a {@link Card} is currently selected.
	 * 
	 * @param card The <code>Card</code> to test.
	 * @return <code>true</code> if the specified <code>Card</code> is selected, otherwise <code>false</code>.
	 */
	public boolean isSelectedCard(Card card) {
		return selectedCards.contains(card);
	}
	
	
	/**
	 * Records the starting <code>Point</code> of a mouse drag. Moves selected {@link Card} to the top of any cards it is touching. 
	 * 
	 * @param point The starting location of the drag.
	 */
	public void dragStart(Point point) {
		prevPoint = point;
		hold();
	}
	
	/**
	 * Called while the mouse is being dragged to update the position and Z-value of the selected {@link Card}.
	 * 
	 * @param point The current location of the mouse.
	 */
	public boolean dragContinue(Point point) {
		int dx = point.x - prevPoint.x;
		int dy = point.y - prevPoint.y;
		prevPoint = point;

		return moveBy(new Point(dx,dy));

	}
	
	/**
	 * Ends a drag??
	 * 
	 * @param point **NOT USED**
	 */
	public void dragEnd(Point point) {
		release();
	}
	
	
	/**
	* Returns a List of Cards.
	*/
	public CardSet getCards() {
		return controller.getRoom().getTable().getCards();
	}
	
	public CardSet getSelectedCards() {
		return selectedCards;
	}
	
	public boolean isSelected() {
		return !selectedCards.isEmpty();
	}
	

	public void setTableSize(Dimension d){
		getTable().setSize(d);
	}
	
	public Dimension getTableSize(){
		return getTable().getSize();
	}
	

	/**
	 * @return the connection
	 */
	public Connection getConnection() {
		return connection;
	}

	/**
	 * @return the model
	 */
	public Controller getModel() {
		return controller;
	}
	
	public void setPlayerListener(PlayerListener pl) {
		// TODO Auto-generated method stub
		
	}
	
	public void serverMoveBy(CardSet cards, int x, int y){
		for(Card c: cards){
			c.setLocation(c.getLocation().x + x, c.getLocation().y + y);
			c.setZ(findHighestZ(c)+1);
		}
	}

	public void syncFromServer() throws IOException {
		connection.sendCommand(Commands.CMD_SYNC, null);
	}
	
	
	public int getNumberOfCards() {
		return selectedCards.size();
	}
	
	
	///////////////////////////////////////////////////////////
	
	/**
	The <code>Player</code> takes control the selected cards.
	*/
	public void hold() {
		ArrayList<Card> cardsLeftToPlace = new ArrayList<Card>(selectedCards);
		ZComparator.sort(cardsLeftToPlace);
		while (!cardsLeftToPlace.isEmpty()) {
			Card c = cardsLeftToPlace.remove(0);
			//fix z
			c.setZ(findHighestZ(c,cardsLeftToPlace) + 1);
		}
		alert();
	
	}
	
	/**
	The <code>Player</code> relinquishes control of the selected cards.
	*/
	public void release() {
		
		for(Card card: selectedCards) {
			int x = card.getLocation().x;
			int y = card.getLocation().y;
			
			if(snap == true) {
				x = snapX*Math.round((float)x/(float)snapX);
				y = snapY*Math.round((float)y/(float)snapY);
			}
			card.setLocation(x, y);
		}
		
		alert();
		sendInfoAcrossServer();
		
	}
	
	public void setSnap(boolean s) {
		snap = s;
	}
	
	//////// the following methods should only be called when player does not have control.
	
	public void select(Set<Card> cards) {
		
		selectedCards = (CardSet)cards;
		Dbg.println("SELECT");
		
		if(cards.isEmpty()) {
			sendDeselect();
		} else {
			sendSelect(getSelectedCardIds());
		}
		
	}
	
	
	//////// the following methods should only be called when player has control.
	
	/**
	The player drops some of the cards they are currently controlling.
	*/
	public void dropSelection(Set<Card> cards) {
		heldCards.removeAll(cards);
	}
	
	/**
	The <code>Player</code> moves the selected cards.  Must be preceded by a call to <code>pickUp</code>.
	@param point the amount to offset the cards by.
	*/
	public boolean moveBy(Point point){
		int dx = point.x;
		int dy = point.y;
		boolean hugesuccess = true;
		
		ArrayList<Card> cardsLeftToPlace = new ArrayList<Card>(selectedCards);
		ZComparator.sort(cardsLeftToPlace);
		while (!cardsLeftToPlace.isEmpty()) {
			Card c = cardsLeftToPlace.remove(0);
			Point oldLoc = c.getLocation();
			hugesuccess = hugesuccess && c.setLocation(new Point(oldLoc.x + dx, oldLoc.y + dy));
			//fix z
			c.setZ(findHighestZ(c,cardsLeftToPlace) + 1);
		}
		
		sendMoveBy(point);
		alert();
		return hugesuccess;
	}
	
	public void flip() {
		
		if (selectedCards.isEmpty()) {
			return;
		}
		for (Card c: selectedCards) {
			c.toggleFaceUp();
		}
		alert();
	}
	
	public Table getTable() {
		return getRoom().getTable();
	}

	
	public int findHighestZ(Card selected) {
		return findHighestZ(selected, new ArrayList<Card>());
	}
	
	/**
	 * Calculates the highest Z-index of all cards intersecting with a given {@link Card}.
	 * 
	 * @param selected The selected <code>Card</code>.
	 * @param except do not count these cards' z value, when finding the highest value.  <code>selected</code> will be automatically considered to be in this group.
	 * @return The highest Z-index found.
	 */
	private int findHighestZ(Card selected, List<Card> except) {
		
		CardSet cards = getRoom().getTable().getCards();
		int highestZ = 0;
	
		for(Card card: cards){
			
			if(except.contains(card)) continue;
			if(selected == card) continue;
			
			if(selected.getRectangle().intersects(card.getRectangle())){//intersects with point
				if(card.getZ() > highestZ){//check if highest z
					highestZ = card.getZ();//set c and z
				}
			}
		}
		
		return highestZ;
	}
	
	private Room getRoom() {
		return getModel().getRoom();
	}
	
	public int[] getHeldCardIds() {
		return heldCards.getCardIDs();
	}

	/**
	 * @return the selectedCard Ids
	 */
	public int[] getSelectedCardIds() {
		return selectedCards.getCardIDs();
	}

	public void moveBy(int x, int y) {
		moveBy(new Point(x,y));
	}
	
	public void shuffle(){
		selectedCards.shuffle();
		//updateServer();
	}
	
	
	
	public void sendToBack() {
// 		int numcards = selectedCards.size();
		for (Card c: selectedCards) {
			c.setZ(0);
		}
		alert();
		sendInfoAcrossServer();
	}
}
